iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
Software Development

深入淺出Java 30天系列 第 6

Day 6: 避免生成不必要的物件

  • 分享至 

  • xImage
  •  

一般來說,重複使用物件,可以減少不必要的資源浪費並提升效能,這在記憶體短缺的地方,會是一個很重要的原則,所以如果物件是immutable,應該盡量重複使用。

要如何避免生不必要的物件,除了使用static method之外(ex: Boolean.valueOf(String)),減少使用constructor,避免生成新的物件,也是一個方法。舉例來說,String如果是用下面constructor的方式宣告,就會生成新的物件。

String s = new String("abc");

改成下面這種方式,這樣JVM就會重複使用這個物件。

String s = "abc";

而在一些mutable的物件,如果物件裡面的資料很少被改變,也可以把一些使用到的資料或物件儲存到static變數,避免資料沒有變動,也生成新的物件。舉例來說,如果有一個Person的class,包含一個人的基本資料,現在要用生日判斷是否已經成年,如果每次都是使用Calendar的getInstance取得新的instance,會造成不必要的浪費。

import java.util.Date;
import java.util.Calendar;

public class Person {
    private final String name;
    private final Date birthDate;

    public Person(String name, Date birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public boolean isAdult() {
        Calendar now = Calendar.getInstance();
        int currentYear = now.get(Calendar.YEAR);
        int birthYear = birthDate.getYear() + 1900;
        return currentYear - birthYear >= 18;
    }
}

如果可以把now設為static,就可以避免每次呼叫isAdult時,都create一個新的Calendar instance,生成不必要的物件。

import java.util.Date;
import java.util.Calendar;

public class Person {
    private final String name;
    private final Date birthDate;
    private static Calendar now = Calendar.getInstance();

    public Person(String name, Date birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public boolean isAdult() {
        int currentYear = now.get(Calendar.YEAR);
        int birthYear = birthDate.getYear() + 1900;
        return currentYear - birthYear >= 18;
    }
}

雖然只呼叫一次,效能上並不會感到差異,但是如果呼叫1000次,每次都用Calendar的getInstance的方式,總共約花費20 ms,用static欄位cache Calendar instance的方式,總共約花費13 ms,可以看出大量生成物件,是真的會影響效能。

import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.time.Instant;

class Main {
    public static void main(String[] args) throws ParseException {
        long start, end;
        start = System.currentTimeMillis();
        DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date birthDate = dateFormat2.parse("2010-09-13 22:36:01");
        Person john = new Person("John", birthDate);
        System.out.println("Is adult? " + john.isAdult());
        for (int i = 0; i < 1000; i++) {
            john.isAdult();
        }
        end = System.currentTimeMillis();
        System.out.println("execution time: " + (end - start) + " millisecond");
    }
}

在Java 1.5版之後,開始支援autoboxing,指的是在compile的時候,Primitive type(ex: int或long)自動轉換成對應的物件(ex: Integer或Long),這個功能增加了便利性,但在無形中也產生新的物件。以下面的範例為例,i的型別是long,但sum的型別是Long,於是在相加的時候,i會被轉換成Long,這中間會生成一個新的物件,然後在跟sum相加,加完之後,雖然因為autoboxing而產生的物件結束任務了,但不會直接被回收,而是繼續佔用空間,且不斷地autoboxing生成物件,會讓程式耗費更多的時間。

import java.lang.Integer;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.time.Instant;

class Main {
    public static void main(String[] args) throws ParseException {
        long start, end;
        start = System.currentTimeMillis();
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        end = System.currentTimeMillis();
        System.out.println("execution time: " + (end - start) + " millisecond");
    }
}

但如果一開始把sum宣告成long,就不會有autoboxing的問題,在上面的那個範例,花費了2848 ms,下面的範例只花費了685 ms,一個不注意,花費的時間居然差了約4倍。

import java.lang.Integer;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.time.Instant;

class Main {
    public static void main(String[] args) throws ParseException {
        long start, end;
        start = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        end = System.currentTimeMillis();
        System.out.println("execution time: " + (end - start) + " millisecond");
    }
}

不過雖然避免生成不必要的物件是一件好事,但是如果重複使用物件會產生其他的問題或bug,也不一定要堅守這個原則。


上一篇
Day 5: 使用private constructor或enum type實作singleton
下一篇
Day 7: 清除陳舊的object references
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言